# 剑指Offer题解-Day2
# 剑指 Offer 06. 从尾到头打印链表
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
示例 1:
输入:head = [1,3,2]
输出:[2,3,1]
1
2
3
4
2
3
4
限制:
- 0 <= 链表长度 <= 10000
思路:
尝试使用暴力法进行破解。
# 暴力法
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {number[]}
*/
var reversePrint = function(head) {
let stack = []; // 初始化一个数组,用来存放链表的值
while(head) { // 循环终止条件是链表为空
stack.push(head.val) // 将链表的值放入数组中
head = head.next; // 将指针指向下一个链表节点
}
return stack.reverse(); // 将数组倒序后返回
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- 空间复杂度:O(n)。
- 时间复杂度:O(n)。
解析:我们先正序遍历链表,同时将链表的值存入数组中。直到链表为空则停止遍历。最后将数组进行倒置后返回,则是最终结果。
# 辅助栈法
首先,链表的特点是从前到后依次访问节点,而题目的要求是倒序输出节点,考虑使用后进先出的辅助栈进行解题。
思路:依次将链表节点放入辅助栈(数组)中。然后将辅助栈的元素依次弹出并存入数组后返回。
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {number[]}
*/
var reversePrint = function(head) {
let stack = []; // 辅助栈
let result = []; // 初始化待返回的数组
while(head) { // 依次将链表元素存入辅助栈
stack.push(head.val)
head = head.next;
}
while(stack.length) {
result.push(stack.pop()) // 依次将辅助栈内的元素弹出并存入结果数组中
}
return result; // 最后返回结果数组
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- 空间复杂度:O(n)。
- 时间复杂度:O(n)。
# 总结
暴力法和辅助栈法的区别是一个使用数组的API进行元素倒置,一个使用辅助栈进行元素倒置。面试中应该尽量使用辅助栈的思路进行题解,暴力法不能体现出栈的特点。
# 剑指 Offer 24. 反转链表
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
1
2
3
4
2
3
4
限制:
0 <= 节点个数 <= 5000
思路:
翻转链表是一个很经典的算法题,首先考虑使用遍历链表,然后维护额外的指针进行next
的转变,从而达到翻转链表的目的。
# 迭代法
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var reverseList = function(head) {
let cur = head; // cur指针指向链表头部
let pre = null; // pre指针指向链表尾部
let temp = null; // 初始化临时存储变量
while(cur) { // 遍历链表
temp = cur.next; // 首先将当前节点的next指针存储,方便cur指针的前移
cur.next = pre; // 将当前节点的next指针指向前面的节点
pre = cur; // 将前面的节点后移一位到当前节点
cur = temp; // 将当前节点后移一位到next指针指向的节点
}
return pre; // 链表反转完成后,pre指针指向的就是头部节点,返回即可
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- 时间复杂度:O(n)。
- 空间复杂度:O(1)。
解析:通过引入临时变量和pre
变量,达到了改变next
指向的效果。临时变量也在交换变量时进行使用,此处同理。核心思路就是先暂存当前节点的next
指向,然后改变next
指向为pre
,然后将pre
和cur
后移一位。
# 递归
本题也可以使用递归进行处理,通过回溯来修改next
指向。
使用递归进行解题,一定要写递归的终止条件,否则会造成死循环导致内存溢出。
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
//递归函数
const recur = (cur, pre) => {
if (cur === null) return pre; // 递归终止条件
let res = recur(cur.next, cur); // 递归后继节点
cur.next = pre; // 将pre节点指向当前节点的next
return res; // 返回反转链表的头节点
}
var reverseList = function(head) {
return recur(head, null); // 调用递归,最后返回反转链表的头节点
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- 时间复杂度:O(n)。
- 空间复杂度:O(n)。
# 总结
相较于递归,迭代法更容易理解。此处优先掌握迭代法进行题解。而递归更考验逻辑思维,通过后续的题解我们慢慢加强。